import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Hashtable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.io.File;

import Vivienda.Trabajo;

public class ServidorAlarmasEagleEye 
{
	public static Hashtable<String, PublicKey> PUBLIC_KEYS_CASAS = new Hashtable<String, PublicKey>();
	public static String IP_SERVIDOR_MONITOR_1;
	public static int PUERTO_SERVIDOR_MONITOR_1;
	public static String IP_SERVIDOR_MONITOR_2;
	public static int PUERTO_SERVIDOR_MONITOR_2;
	public static int puerto;
	
	public static void main(String[] args) 
	{
		if (args.length != 6) 
		{
			System.out.println("Llamar con parametros: \n [0]: Numero de Hilos ThreadPool \n [1]: Puerto por el cual va a escuchar \n [2]: IP del servidor de monitor ppal callback \n [3]: puerto del servidor de monitor principal callback  \n [4]: ip del servidor de monitor secundario callback \n [5]: puerto dle servidor de monitor secundario callback");
			System.exit(-1);
		}

		int numHilos = Integer.parseInt(args[0]);
		ServidorAlarmasEagleEye.puerto = Integer.parseInt(args[1]);
		
		ServidorAlarmasEagleEye.IP_SERVIDOR_MONITOR_1 = args[2];
		ServidorAlarmasEagleEye.PUERTO_SERVIDOR_MONITOR_1 = Integer.parseInt(args[3]);
		ServidorAlarmasEagleEye.IP_SERVIDOR_MONITOR_2 = args[4];
		ServidorAlarmasEagleEye.PUERTO_SERVIDOR_MONITOR_2 = Integer.parseInt(args[5]);
		
		String numeroPuerto = Trabajo.padLeft("" + ServidorAlarmasEagleEye.puerto, 5);
		String mensajeDeSubscripcion = String.format("03%s", numeroPuerto);
		HiloNuevo.enviarMensajeAServidoresMonitores(mensajeDeSubscripcion);
		
		// inicial el hilo keep alive
		new HiloEnviarKeepAlive(mensajeDeSubscripcion).start();
		
		System.out.println("Se arranca el sistema Central con un pool de " + numHilos + " hilos ");

		// Desencolador de persistencia a MySQL
		new HiloDesencolador().start();
		
		
		try 
		{
			String nombreDirectorio = "c:\\temp\\";
			File directorio = new File(nombreDirectorio); 
			if(!directorio.exists())
				throw new Exception("No existe el directorio " + nombreDirectorio);
			
			if(!directorio.isDirectory())
				throw new Exception("No existe el directorio " + nombreDirectorio);
			
			FilenameFilter filtroPublicKeys = new FilenameFilter() 
			{
			    public boolean accept(File dir, String name) 
			    {
			        return name.startsWith("PublicKey__Casa") && name.endsWith(".key");
			    }
			};

			// Se cargan a memoria principal las Llaves Publicas de todas las casas que hayan en el directorio de trabajo
			File[] archivosEnDirectorio = directorio.listFiles(filtroPublicKeys);
			for(File archivo: archivosEnDirectorio)
			{
				String nombreArchivo = archivo.getName(); 
				int longitudNombre = nombreArchivo.length();
				String codigoCasa = nombreArchivo.substring(longitudNombre - 8, longitudNombre - 4);
				
				// Se lee la llave Publica
				FileInputStream keyfis = new FileInputStream(archivo);
		        byte[] encKey = new byte[keyfis.available()];  
		        keyfis.read(encKey);
		        keyfis.close();
		
		        X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(encKey);
		        
		        KeyFactory keyFactory = KeyFactory.getInstance("DSA", "SUN");
		        PublicKey pubKey = keyFactory.generatePublic(pubKeySpec);
				
				ServidorAlarmasEagleEye.PUBLIC_KEYS_CASAS.put(codigoCasa, pubKey);
				System.out.println("Se cargo el PublicKey de la Casa " + codigoCasa);
			}
			
			// se prende el socket para empezar a escuchar conexiones entrantes
			ServerSocket serverSocket = new ServerSocket(puerto);

			ExecutorService pool = Executors.newFixedThreadPool(numHilos);

			while (true) 
			{
				Socket socketCliente = serverSocket.accept();
				
				HiloNuevo hilo = new HiloNuevo();
				hilo.establecerCliente(socketCliente);
				pool.execute(hilo);
			}
		} 
		catch (IOException e) 
		{
			System.err.println("No se puede escuchar en puerto: "+puerto+". "+ e.getMessage());
			System.exit(-1);
		}
		catch(Exception ex)
		{
			ex.printStackTrace();
		}

	}

}

class HiloEnviarKeepAlive extends Thread
{
	private String mensajeDeSubscripcion;
	
	public HiloEnviarKeepAlive(String mensajeDeSubscripcion)
	{
		this.mensajeDeSubscripcion = mensajeDeSubscripcion;
	}
	
	public void run()
	{
		while(true)
		{
			try
			{
				Thread.sleep(2000);
				HiloNuevo.enviarMensajeAServidoresMonitores(this.mensajeDeSubscripcion);
			}
			catch(Exception ex)
			{
				
			}
		}
	}
}

/*
 * Se encarga de trabajar los mensajes que se reciben desde el monitor
 */
class HiloNuevo extends Thread 
{
	private Socket socketCliente;
	
	public void establecerCliente(Socket socket) 
	{
		this.socketCliente = socket;
		this.setDaemon(true);
	}

	public void run() 
	{
		try 
		{
			BufferedInputStream bis = new BufferedInputStream(socketCliente.getInputStream());
			byte[] datosLeidos = new byte[1024];
			int totalDatosLeidos = bis.read(datosLeidos);
			
			if(totalDatosLeidos <= 0)
				return;
			
			int tamanoFirmaDigital = (int)datosLeidos[0];
			
			byte[] firmaDigital = new byte[tamanoFirmaDigital];
			int i = 1;
			int j = 0;
			while(i <= tamanoFirmaDigital)
			{
				firmaDigital[j] = datosLeidos[i];
				i++;
				j++;
			}
			
			byte[] bytesMensaje = new byte[totalDatosLeidos - tamanoFirmaDigital - 1];
			j = 0;
			while(i < totalDatosLeidos)
			{
				bytesMensaje[j] = datosLeidos[i];
				i++;
				j++;
			}
			
			String mensaje = this.convertirBytesACadena(bytesMensaje);

			String timestamp = mensaje.substring(0, 20);
			String codigoCasa = String.format("%s", mensaje.substring(20, 24));
			
			String mensajeLlamadoCallBack_Exito = String.format("01%s%s", codigoCasa, timestamp);
			String mensajeLlamadoCallBack_BloquearCasa = String.format("02%s%s", codigoCasa, timestamp);
			
			try
			{
				// Primero se verifica la autenticidad del mensaje
				if(!ServidorAlarmasEagleEye.PUBLIC_KEYS_CASAS.containsKey(codigoCasa))
					throw new Exception("No existe la casa en la lista de casas del archivo");
				
				PublicKey publicKeyCasa = ServidorAlarmasEagleEye.PUBLIC_KEYS_CASAS.get(codigoCasa);
				Signature sig = Signature.getInstance("SHA1withDSA", "SUN");
		        sig.initVerify(publicKeyCasa);
		        sig.update(bytesMensaje);
		        
		        boolean mensajeAutentico = sig.verify(firmaDigital);
		        
		        if(!mensajeAutentico)
		        	throw new Exception("El mensaje NO es autentico");
		        
		        try
		        {
		        	//TODO: arreglar lo de MySQL
		        	EagleEyeAlarma.generarAlarma(mensaje);
		        	
		        	HiloNuevo.enviarMensajeAServidorMonitor(mensajeLlamadoCallBack_Exito);
		        }
		        catch(Exception ex)
		        {
		        	ex.printStackTrace(); 
		        }
			}
			catch(Exception ex)
			{
				System.out.println(ex.getMessage());
				HiloNuevo.enviarMensajeAServidorMonitor(mensajeLlamadoCallBack_BloquearCasa);
			}
			
			socketCliente.getInputStream().close();
			this.socketCliente.close();
		} 
		catch (IOException e2) 
		{
			e2.printStackTrace();
		}
	}
	
	private String convertirBytesACadena(byte[] bytes)
	{
		String resultado = "";
		
		for(byte b: bytes)
		{
			resultado += (char)b;
		}
		
		return resultado;
	}
	
	public static void enviarMensajeAServidorMonitor(String mensaje)
	{
		Socket socketMonitor = null;
		
		int maxVecesIntentos = 2;
		int vecesIntentos = 1;
		while(vecesIntentos <= maxVecesIntentos)
		{
			try
			{
				socketMonitor = new Socket(ServidorAlarmasEagleEye.IP_SERVIDOR_MONITOR_1, ServidorAlarmasEagleEye.PUERTO_SERVIDOR_MONITOR_1);
				
				PrintWriter escrituraServidorUrbanizacion = new PrintWriter(socketMonitor.getOutputStream(), true);
				escrituraServidorUrbanizacion.println(mensaje);
				escrituraServidorUrbanizacion.close();
				
				socketMonitor.close();
				
				vecesIntentos = maxVecesIntentos + 1; // para forzar la salida del while 
			}
			catch(Exception ex2)
			{
				try
				{
					socketMonitor = new Socket(ServidorAlarmasEagleEye.IP_SERVIDOR_MONITOR_2, ServidorAlarmasEagleEye.PUERTO_SERVIDOR_MONITOR_2);
					
					PrintWriter escrituraServidorUrbanizacion = new PrintWriter(socketMonitor.getOutputStream(), true);
					escrituraServidorUrbanizacion.println(mensaje);
					escrituraServidorUrbanizacion.close();
					
					socketMonitor.close();
					
					vecesIntentos = maxVecesIntentos + 1; // para forzar la salida del while
				}
				catch(Exception ex3)
				{
					vecesIntentos++;
				}
			}
		}
	}
	
	public static void enviarMensajeAServidoresMonitores(String mensaje)
	{
		Socket socketMonitor = null;
		
		try
		{
			socketMonitor = new Socket(ServidorAlarmasEagleEye.IP_SERVIDOR_MONITOR_1, ServidorAlarmasEagleEye.PUERTO_SERVIDOR_MONITOR_1);
			
			PrintWriter escrituraServidorUrbanizacion = new PrintWriter(socketMonitor.getOutputStream(), true);
			escrituraServidorUrbanizacion.println(mensaje);
			escrituraServidorUrbanizacion.close();
			
			socketMonitor.close();
		}
		catch(Exception ex)
		{
			//ex.printStackTrace();
		}
		
		try
		{
			socketMonitor = new Socket(ServidorAlarmasEagleEye.IP_SERVIDOR_MONITOR_2, ServidorAlarmasEagleEye.PUERTO_SERVIDOR_MONITOR_2);
			
			PrintWriter escrituraServidorUrbanizacion = new PrintWriter(socketMonitor.getOutputStream(), true);
			escrituraServidorUrbanizacion.println(mensaje);
			escrituraServidorUrbanizacion.close();
			
			socketMonitor.close();
		}
		catch(Exception ex)
		{
			//ex.printStackTrace();
		}
	}

}
